package com.spark.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * @author fuqiang
 * @Description 授权认证服务
 * @Date 2020/10/10
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    /**令牌存储策略*/
    @Autowired
    private TokenStore tokenStore;
    /**客户端查询服务*/
    @Autowired
    private ClientDetailsService clientDetailsService;
    /**授权码服务*/
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    /**认证管理器*/
    @Autowired
    private AuthenticationManager authenticationManager;
    /**token生成器*/
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;
    /**密码加密器*/
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 1.将客户端信息存储到数据库
     * @param dataSource
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 2.查询认证客户
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
         clients.withClientDetails(clientDetailsService);
    }

    /**
     * 3.配置令牌服务
     * 此bean 接口定义了一些操作使得你可以对令牌进行一些必要的管理，令牌可以被用来 加载身份信息，里面包含了这个令牌的相关权限
     * 你可以使用它来修改令牌的格式和令牌的存储。默认的，当它尝试创建一个令牌的时 候，
     * 是使用随机值来进行填充的，除了持久化令牌是委托一个 TokenStore
     * 接口来实现以外，这个类几乎帮你做了 所有的事情
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        // 客户端详情服务
        service.setClientDetailsService(clientDetailsService);
        // 支持刷新令牌
        service.setSupportRefreshToken(true);
        // 令牌存储策略
        service.setTokenStore(tokenStore);
        // 令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);
        // 令牌默认有效期2小时
        service.setAccessTokenValiditySeconds(7200);
        // 刷新令牌默认有效期3天
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }

    /**
         此配置方法有以下几个用处：
         不同的授权类型（Grant Types）需要设置不同的类：
         authenticationManager：当授权类型为密码模式(password)时，需要设置此类
         AuthorizationCodeServices： 授权码模式(authorization_code) 下需要设置此类，用于实现授权码逻辑
         implicitGrantService：隐式授权模式设置此类。
         tokenGranter：自定义授权模式逻辑

         通过pathMapping<默认链接,自定义链接> 方法修改默认的端点URL
         /oauth/authorize：授权端点。
         /oauth/token：令牌端点。
         /oauth/conﬁrm_access：用户确认授权提交端点。
         /oauth/error：授权服务错误信息端点。
         /oauth/check_token：用于资源服务访问的令牌解析端点。
         /oauth/token_key：提供公有密匙的端点，如果你使用JWT令牌的话。


         通过tokenStore来定义Token的存储方式和生成方式：
         InMemoryTokenStore
         JdbcTokenStore
         JwtTokenStore
         RedisTokenStore
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices)
                .tokenServices(tokenService())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    /**
     * 设置授权码模式的授权码如何 存取，
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        //设置授权码模式的授权码如何存取
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     *  对端点的访问控制
     *  对oauth/check_token，oauth/token_key访问控制，可以设置isAuthenticated()、permitAll()等权限
     *  这块的权限控制是针对应用的，而非用户，比如当设置了isAuthenticated()，必须在请求头中添加应用的id和密钥才能访问
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security){
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

}
